/*
* Copyright (C) 2012 Holger Oehm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.holger_oehm.usb.hid.windows;
import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.platform.win32.Guid;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.SetupApi;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;
import de.holger_oehm.usb.hid.HiDeviceException.HIDDeviceNotFoundException;
import de.holger_oehm.usb.hid.HiDevice;
import de.holger_oehm.usb.hid.USBAddress;
public class HiDeviceWin implements HiDevice {
static {
setPreserveLastError(true);
}
private static final int ERROR_NO_MORE_ITEMS = 259;
private final USBAddress address;
private HANDLE handle = WinBase.INVALID_HANDLE_VALUE;
public HiDeviceWin(final USBAddress address) {
this.address = address;
open();
}
@SuppressWarnings("deprecation")
private static void setPreserveLastError(final boolean bool) {
Native.setPreserveLastError(bool);
}
private void open() {
final Guid.GUID.ByReference hidDevices = getHidDevicesGuid();
final HANDLE deviceInfoList = SetupApi.INSTANCE.SetupDiGetClassDevs(hidDevices, null, null,
SetupApi.DIGCF_DEVICEINTERFACE | SetupApi.DIGCF_PRESENT);
if (WinBase.INVALID_HANDLE_VALUE == deviceInfoList) {
throw new LastErrorException(Native.getLastError());
}
try {
for (int index = 0;; index++) {
// Loop exits with HIDDeviceNotFoundException
final String devicePath = getDevicePath(hidDevices, deviceInfoList, index);
final WinNT.HANDLE deviceHandle = getDeviceHandle(devicePath);
final USBAddress usbAddress = getUsbAddress(deviceHandle);
if (usbAddress.equals(address)) {
handle = deviceHandle;
break;
} else {
Kernel32.INSTANCE.CloseHandle(deviceHandle);
}
}
} finally {
SetupApi.INSTANCE.SetupDiDestroyDeviceInfoList(deviceInfoList);
}
}
private Guid.GUID.ByReference getHidDevicesGuid() {
final Guid.GUID.ByReference hidDevices = new Guid.GUID.ByReference();
Hid.INSTANCE.HidD_GetHidGuid(hidDevices);
return hidDevices;
}
private USBAddress getUsbAddress(final WinNT.HANDLE deviceHandle) {
final HidAttributes attributes = new HidAttributes();
Hid.INSTANCE.HidD_GetAttributes(deviceHandle, attributes);
return new USBAddress(attributes.vendorID, attributes.productID);
}
private WinNT.HANDLE getDeviceHandle(final String devicePath) {
final int shareMode = WinNT.FILE_SHARE_READ | WinNT.FILE_SHARE_WRITE;
return Kernel32.INSTANCE.CreateFile(devicePath, 0, shareMode, null, WinNT.OPEN_EXISTING, WinNT.FILE_FLAG_OVERLAPPED,
(WinNT.HANDLE) null);
}
private String getDevicePath(final Guid.GUID.ByReference hidDevices, final HANDLE hDevInfo, final int index)
throws HIDDeviceNotFoundException {
final SetupApi.SP_DEVICE_INTERFACE_DATA.ByReference deviceInterfaceData = new SetupApi.SP_DEVICE_INTERFACE_DATA.ByReference();
if (!SetupApi.INSTANCE.SetupDiEnumDeviceInterfaces(hDevInfo, null, hidDevices, index, deviceInterfaceData)) {
final int errno = Native.getLastError();
if (errno == ERROR_NO_MORE_ITEMS) {
throw new HIDDeviceNotFoundException("no device with address " + address + " found");
}
throw new LastErrorException(errno);
}
// get length of path
final IntByReference requestLength = new IntByReference();
SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hDevInfo, deviceInterfaceData, null, 0, requestLength, null);
// prepare actual result data structure
final DeviceInterfaceDetailData detailData = new DeviceInterfaceDetailData(requestLength.getValue());
detailData.write();
if (!SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hDevInfo, deviceInterfaceData, detailData.getPointer(),
requestLength.getValue(), requestLength, null)) {
final int errno = Native.getLastError();
throw new LastErrorException(errno);
}
detailData.read();
return Native.toString(detailData.devicePath);
}
private boolean isOpened() {
return handle != WinBase.INVALID_HANDLE_VALUE;
}
@Override
public void setReport(final int reportNumber, final byte[] report) {
if (!isOpened()) {
throw new IllegalStateException("not opened");
}
final byte[] buffer = new byte[report.length + 1];
buffer[0] = (byte) reportNumber;
System.arraycopy(report, 0, buffer, 1, report.length);
Hid.INSTANCE.HidD_SetOutputReport(handle, buffer, new NativeLong(buffer.length));
}
@Override
public void close() {
final HANDLE deviceHandle = handle;
handle = WinBase.INVALID_HANDLE_VALUE;
Kernel32.INSTANCE.CloseHandle(deviceHandle);
}
}